<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      * {
        padding: 0;
        margin: 0;
      }
      html,
      body {
        width: 100%;
        height: 100vh;
        overflow: hidden;
      }
      body {
        background-image: repeating-radial-gradient(
            circle at center center,
            transparent 0px,
            transparent 8px,
            rgba(255, 255, 255, 0.05) 8px,
            rgba(255, 255, 255, 0.05) 11px,
            transparent 11px,
            transparent 17px,
            rgba(255, 255, 255, 0.05) 17px,
            rgba(255, 255, 255, 0.05) 25px,
            transparent 25px,
            transparent 38px,
            rgba(255, 255, 255, 0.05) 38px,
            rgba(255, 255, 255, 0.05) 42px
          ),
          repeating-radial-gradient(
            circle at center center,
            rgb(0, 0, 0) 0px,
            rgb(0, 0, 0) 11px,
            rgb(0, 0, 0) 11px,
            rgb(0, 0, 0) 19px,
            rgb(0, 0, 0) 19px,
            rgb(0, 0, 0) 24px,
            rgb(0, 0, 0) 24px,
            rgb(0, 0, 0) 33px,
            rgb(0, 0, 0) 33px,
            rgb(0, 0, 0) 44px,
            rgb(0, 0, 0) 44px,
            rgb(0, 0, 0) 46px
          );
        background-size: 60px 60px;
      }
      #canvas {
        width: 100%;
        height: 100%;
      }
    </style>
  </head>
  <body>
    <canvas id="canvas"></canvas>
  </body>

  <script>
    /*WaveBall.js*/
    class WaveBall {
      constructor(options) {
        this.x = 0 // x轴坐标
        this.y = 0 // y轴坐标
        this.size = 200 // 大小(直径)
        this.color = 'rgba(55, 133, 207, .75)' // 颜色
        this.waveWidth = 0.025 // 波频
        this.waveHeight = 5.6 // 震幅
        this.progress = 0 // 进度
        Object.assign(this, options)
        this.ctx = null // 环境
        this.startX = 0 // 初始位置
        this.offsetX = [0, 0] // 偏移位置
        this.vx = [0.156, 0.097] // x轴增加量
        this.bgColor = this.color // 背景色
        return this
      }
      render(ctx) {
        this.ctx = ctx
        const { color, size } = this
        this.bgColor = ctx.createLinearGradient(
          size / 2,
          size / 2,
          size / 2,
          size
        )
        this.bgColor.addColorStop(0, color)
        this.bgColor.addColorStop(1, color)
        this.drawBall()
        this.drawWave()
        return this
      }
      drawBall() {
        const { size, ctx, x, y, color } = this
        ctx.save()
        ctx.lineWidth = 3
        ctx.strokeStyle = color
        ctx.translate(x, y)
        ctx.beginPath()
        ctx.arc(0, 0, size / 2, 0, 2 * Math.PI)
        ctx.stroke()
        ctx.restore()
      }
      drawWave(n = 0) {
        this.offsetX[n] += this.vx[n]
        const {
          startX,
          size,
          ctx,
          offsetX,
          x,
          y,
          bgColor,
          waveWidth,
          waveHeight,
          progress,
        } = this
        let height = (-progress / 100) * size - 5
        ctx.save()
        ctx.translate(x, y)
        ctx.beginPath()
        ctx.arc(0, 0, size / 2, 0, 2 * Math.PI)
        ctx.clip()
        ctx.beginPath()
        for (let i = -size / 2; i < size / 2; i++) {
          const h = waveHeight * Math.sin((startX + i) * waveWidth + offsetX[n])
          ctx.lineTo(i, h + size / 2 + height)
        }
        ctx.lineTo(size / 2, size / 2)
        ctx.lineTo(-size / 2, size / 2)
        ctx.fillStyle = bgColor
        ctx.fill()
        ctx.restore()
      }
      update() {
        this.drawWave(0)
        this.drawWave(1)
        this.drawBall()
      }
    }
    
    class Application {
      constructor() {
        this.canvas = null // 画布
        this.ctx = null // 环境
        this.w = 0 // 画布宽
        this.h = 0 // 画布高
        this.init()
      }
      init() {
        // 初始化
        this.canvas = document.getElementById('canvas')
        this.ctx = this.canvas.getContext('2d')
        window.addEventListener('resize', this.reset.bind(this))
        this.reset()
        this.render()
        this.step()
      }
      reset() {
        // 屏幕改变
        this.w = this.canvas.width = this.ctx.width = window.innerWidth
        this.h = this.canvas.height = this.ctx.height = window.innerHeight
        this.render()
      }
      render() {
        // 主渲染
        const { w, h, ctx } = this
        this.waveBall = new WaveBall({
          x: w / 2,
          y: h / 2,
          size: h * 0.45,
          progress: 50,
        }).render(ctx)
      }
      step(delta) {
        // 重绘
        const { w, h, ctx } = this
        requestAnimationFrame(this.step.bind(this))
        ctx.clearRect(0, 0, w, h)
        this.waveBall.progress += 0.1
        this.waveBall.progress %= 100
        this.waveBall.update()
      }
    }
    window.onload = new Application()
  </script>
</html>
